/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.handler.component;

import org.apache.lucene.search.Query;
import org.apache.lucene.search.grouping.SearchGroup;
import org.apache.lucene.search.grouping.TopGroups;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.RTimer;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.DocListAndSet;
import org.apache.solr.search.QParser;
import org.apache.solr.search.SortSpec;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.search.grouping.GroupingSpecification;
import org.apache.solr.search.grouping.distributed.command.QueryCommandResult;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class is experimental and will be changing in the future.
 * 
 * @version $Id: ResponseBuilder.java 1171968 2011-09-17 12:46:06Z mvg $
 * @since solr 1.3
 */
public class ResponseBuilder {
	public SolrQueryRequest req;
	public SolrQueryResponse rsp;
	public boolean doHighlights;
	public boolean doFacets;
	public boolean doStats;
	public boolean doTerms;

	private boolean needDocList = false;
	private boolean needDocSet = false;
	private int fieldFlags = 0;
	private boolean debug = false;

	private QParser qparser = null;
	private String queryString = null;
	private Query query = null;
	private List<Query> filters = null;
	private SortSpec sortSpec = null;
	private GroupingSpecification groupingSpec;

	private DocListAndSet results = null;
	private NamedList<Object> debugInfo = null;
	private RTimer timer = null;

	private Query highlightQuery = null;

	public List<SearchComponent> components;

	// ////////////////////////////////////////////////////////
	// ////////////////////////////////////////////////////////
	// // Distributed Search section
	// ////////////////////////////////////////////////////////
	// ////////////////////////////////////////////////////////

	public static final String FIELD_SORT_VALUES = "fsv";
	public static final String SHARDS = "shards";
	public static final String IDS = "ids";

	/***
	 * public static final String NUMDOCS = "nd"; public static final String
	 * DOCFREQS = "tdf"; public static final String TERMS = "terms"; public
	 * static final String EXTRACT_QUERY_TERMS = "eqt"; public static final
	 * String LOCAL_SHARD = "local"; public static final String DOC_QUERY =
	 * "dq";
	 ***/

	public static int STAGE_START = 0;
	public static int STAGE_PARSE_QUERY = 1000;
	public static int STAGE_TOP_GROUPS = 1500;
	public static int STAGE_EXECUTE_QUERY = 2000;
	public static int STAGE_GET_FIELDS = 3000;
	public static int STAGE_DONE = Integer.MAX_VALUE;

	public int stage; // What stage is this current request at?

	// The address of the Shard
	public String[] shards;
	public int shards_rows = -1;
	public int shards_start = -1;
	public List<ShardRequest> outgoing; // requests to be sent
	public List<ShardRequest> finished; // requests that have received responses
										// from all shards

	public int getShardNum(String shard) {
		for (int i = 0; i < shards.length; i++) {
			if (shards[i] == shard || shards[i].equals(shard))
				return i;
		}
		return -1;
	}

	public void addRequest(SearchComponent me, ShardRequest sreq) {
		outgoing.add(sreq);
		if ((sreq.purpose & ShardRequest.PURPOSE_PRIVATE) == 0) {
			// if this isn't a private request, let other components modify it.
			for (SearchComponent component : components) {
				if (component != me) {
					component.modifyRequest(this, me, sreq);
				}
			}
		}
	}

	public GlobalCollectionStat globalCollectionStat;

	public Map<Object, ShardDoc> resultIds;
	// Maps uniqueKeyValue to ShardDoc, which may be used to
	// determine order of the doc or uniqueKey in the final
	// returned sequence.
	// Only valid after STAGE_EXECUTE_QUERY has completed.

	public FacetComponent.FacetInfo _facetInfo;
	/* private... components that don't own these shouldn't use them */
	SolrDocumentList _responseDocs;
	StatsInfo _statsInfo;
	TermsComponent.TermsHelper _termsHelper;

	// Context fields for grouping
	public final Map<String, Collection<SearchGroup<String>>> mergedSearchGroups = new HashMap<String, Collection<SearchGroup<String>>>();
	public final Map<String, Map<SearchGroup<String>, String>> searchGroupToShard = new HashMap<String, Map<SearchGroup<String>, String>>();
	public final Map<String, TopGroups<String>> mergedTopGroups = new HashMap<String, TopGroups<String>>();
	public final Map<String, QueryCommandResult> mergedQueryCommandResults = new HashMap<String, QueryCommandResult>();
	public final Map<Object, SolrDocument> retrievedDocuments = new HashMap<Object, SolrDocument>();

	/**
	 * Utility function to add debugging info. This will make sure a valid
	 * debugInfo exists before adding to it.
	 */
	public void addDebugInfo(String name, Object val) {
		if (debugInfo == null) {
			debugInfo = new SimpleOrderedMap<Object>();
		}
		debugInfo.add(name, val);
	}

	// -------------------------------------------------------------------------
	// -------------------------------------------------------------------------

	public boolean isDebug() {
		return debug;
	}

	public void setDebug(boolean debug) {
		this.debug = debug;
	}

	public NamedList<Object> getDebugInfo() {
		return debugInfo;
	}

	public void setDebugInfo(NamedList<Object> debugInfo) {
		this.debugInfo = debugInfo;
	}

	public int getFieldFlags() {
		return fieldFlags;
	}

	public void setFieldFlags(int fieldFlags) {
		this.fieldFlags = fieldFlags;
	}

	public List<Query> getFilters() {
		return filters;
	}

	public void setFilters(List<Query> filters) {
		this.filters = filters;
	}

	public Query getHighlightQuery() {
		return highlightQuery;
	}

	public void setHighlightQuery(Query highlightQuery) {
		this.highlightQuery = highlightQuery;
	}

	public boolean isNeedDocList() {
		return needDocList;
	}

	public void setNeedDocList(boolean needDocList) {
		this.needDocList = needDocList;
	}

	public boolean isNeedDocSet() {
		return needDocSet;
	}

	public void setNeedDocSet(boolean needDocSet) {
		this.needDocSet = needDocSet;
	}

	public QParser getQparser() {
		return qparser;
	}

	public void setQparser(QParser qparser) {
		this.qparser = qparser;
	}

	public String getQueryString() {
		return queryString;
	}

	public void setQueryString(String qstr) {
		this.queryString = qstr;
	}

	public Query getQuery() {
		return query;
	}

	public void setQuery(Query query) {
		this.query = query;
	}

	public DocListAndSet getResults() {
		return results;
	}

	public void setResults(DocListAndSet results) {
		this.results = results;
	}

	public SortSpec getSortSpec() {
		return sortSpec;
	}

	public void setSortSpec(SortSpec sort) {
		this.sortSpec = sort;
	}

	public GroupingSpecification getGroupingSpec() {
		return groupingSpec;
	}

	public void setGroupingSpec(GroupingSpecification groupingSpec) {
		this.groupingSpec = groupingSpec;
	}

	public boolean grouping() {
		return groupingSpec != null;
	}

	public RTimer getTimer() {
		return timer;
	}

	public void setTimer(RTimer timer) {
		this.timer = timer;
	}

	public static class GlobalCollectionStat {
		public final long numDocs;

		public final Map<String, Long> dfMap;

		public GlobalCollectionStat(int numDocs, Map<String, Long> dfMap) {
			this.numDocs = numDocs;
			this.dfMap = dfMap;
		}
	}

	/**
	 * Creates a SolrIndexSearcher.QueryCommand from this ResponseBuilder.
	 * TimeAllowed is left unset.
	 */
	public SolrIndexSearcher.QueryCommand getQueryCommand() {
		SolrIndexSearcher.QueryCommand cmd = new SolrIndexSearcher.QueryCommand();
		cmd.setQuery(getQuery()).setFilterList(getFilters())
				.setSort(getSortSpec().getSort())
				.setOffset(getSortSpec().getOffset())
				.setLen(getSortSpec().getCount()).setFlags(getFieldFlags())
				.setNeedDocSet(isNeedDocSet());
		return cmd;
	}

	/**
	 * Sets results from a SolrIndexSearcher.QueryResult.
	 */
	public void setResult(SolrIndexSearcher.QueryResult result) {
		setResults(result.getDocListAndSet());
		if (result.isPartialResults()) {
			rsp.getResponseHeader().add("partialResults", Boolean.TRUE);
		}
	}
}
